
#include <BStyle>
#include <BPainter>
#include <BMouseEvent>

#include "member_BScrollArea.h"
#include "member_BWidget.h"
#include "member_BStyle.h"
#include "member_BObject.h"

using namespace BWE;

#define member					(*(member_BScrollArea*)_ptr)
#define member_allocate()		_ptr = new member_BScrollArea(this)
#define member_release()		delete (member_BScrollArea*)_ptr

BScrollArea::BScrollArea(BWidget* parent) : BWidget(parent)
{
	member_allocate();
}
BScrollArea::BScrollArea(const BString& title, BWidget* parent) : BWidget(parent)
{
	member_allocate();
	this->setTitle(title);
	member.titleVisible = true;
}
BScrollArea::BScrollArea(BIcon* icon, const BString& title, BWidget* parent) : BWidget(parent)
{
	member_allocate();
	this->setIcon(icon);
	this->setTitle(title);
	member.titleVisible = true;
}
BScrollArea::~BScrollArea()
{
	member_release();
}

bool BScrollArea::setTitle(const BString& title)
{
	if (BWidget::setTitle(title))
	{
		member.dirty = true;
		this->fresh();
		return true;
	}
	return false;
}
bool BScrollArea::setIcon(BIcon* icon)
{
	if (BWidget::setIcon(icon))
	{
		member.dirty = true;
		this->fresh();
		return true;
	}
	return false;
}

void BScrollArea::setTitleVisible(bool titleVisible)
{
	if (member.titleVisible != titleVisible)
	{
		member.titleVisible = titleVisible;
		member.dirty = true;
		this->fresh();
	}
}
bool BScrollArea::titleVisible() const
{
	return member.titleVisible;
}

void BScrollArea::setTitleAlign(Align titleAlign)
{
	if (member.alignment.ia_align != titleAlign)
	{
		member.alignment.ia_align = titleAlign;
		member.dirty = true;
		this->fresh();
	}
}
Align BScrollArea::titleAlign() const
{
	return member.alignment.ia_align;
}

void BScrollArea::setTitleForm(Form titleForm)
{
	if (member.alignment.ia_form != titleForm)
	{
		member.alignment.ia_form = titleForm;
		member.dirty = true;
		this->emit(Signal_FormChanged, titleForm);
		this->adjust();
	}
}
Form BScrollArea::titleForm() const
{
	return member.alignment.ia_form;
}

void BScrollArea::setIconSize(const BSize& iconSize)
{
	if (member.iconSize != iconSize)
	{
		member.iconSize = iconSize;
		this->fresh();
	}
}
const BSize& BScrollArea::iconSize() const
{
	return member.iconSize;
}

void BScrollArea::setAreaScale(float widthScale, float heightScale)
{
	setAreaScale(BPointf(widthScale, heightScale));
}
void BScrollArea::setAreaScale(const BPointf& areaScale)
{
	if (member.areaScale != areaScale)
	{
		member.areaScale = areaScale;
		if (Orientation orientation = member.adjustRealArea())
		{
			this->scrolling(orientation);
			member.adjustScrollBar();
			this->fresh();
		}
		this->emit(Signal_ScaleChanged, areaScale);
		this->fresh();
	}
}
const BPointf& BScrollArea::areaScale() const
{
	return member.areaScale;
}

void BScrollArea::setAreaAlign(Align areaAlign)
{
	if (member.areaAlign != areaAlign)
	{
		member.areaAlign = areaAlign;
		if (Orientation orientation = member.adjustRealArea())
		{
			this->scrolling(orientation);
			member.adjustScrollBar();
			this->fresh();
		}
	}
}
Align BScrollArea::areaAlign() const
{
	return member.areaAlign;
}

void BScrollArea::setAreaImage(BImage* areaImage)
{
	if (member.areaImage != areaImage)
	{
		member.areaImage = areaImage;
		this->fresh();
	}
}
const BImage* BScrollArea::areaImage() const
{
	return member.areaImage;
}
BImage* BScrollArea::areaImage()
{
	return member.areaImage;
}

void BScrollArea::setAreaPolicy(Policy widthPolicy, Policy heightPolicy)
{
	setAreaPolicy(BSizePolicy(widthPolicy, heightPolicy));
}
void BScrollArea::setAreaPolicy(const BSizePolicy& areaPolicy)
{
	if (member.areaPolicy != areaPolicy)
	{
		member.areaPolicy = areaPolicy;
		if (Orientation orientation = member.adjustRealArea())
		{
			this->scrolling(orientation);
			member.adjustScrollBar();
			bool widthDirty = !member.horScrollHint && member.areaPolicy.widthPolicy() != areaPolicy.widthPolicy();
			bool heightDirty = !member.verScrollHint && member.areaPolicy.heightPolicy() != areaPolicy.heightPolicy();
			if (widthDirty || heightDirty)
			{
				this->adjust();
			}
		}
		this->fresh();
	}
}
const BSizePolicy& BScrollArea::areaPolicy() const
{
	return member.areaPolicy;
}

void BScrollArea::setAreaSize(int width, int height)
{
	BRect area = member.rawArea;
	area.setSize(width, height);
	this->setArea(area);
}
void BScrollArea::setAreaSize(const BSize& size)
{
	BRect area = member.rawArea;
	area.setSize(size);
	this->setArea(area);
}
BSize BScrollArea::areaSize() const
{
	return member.area.size();
}

void BScrollArea::setAreaWidth(int width)
{
	BSize size(bMax(1, width), member.rawArea.height());
	this->setAreaSize(size);
}
int BScrollArea::areaWidth() const
{
	return member.area.width();
}

void BScrollArea::setAreaHeight(int height)
{
	BSize size(member.rawArea.width(), bMax(1, height));
	this->setAreaSize(size);
}
int BScrollArea::areaHeight() const
{
	return member.area.height();
}

void BScrollArea::setArea(int x, int y, int width, int height)
{
	setArea(BRect(x, y, width, height));
}
void BScrollArea::setArea(const BRect& area)
{
	if (member.area != area)
	{
		int areaWidth = member.area.width();
		int areaHeight = member.area.height();
		member.rawArea = area;
		if (Orientation orientation = member.adjustRealArea())
		{
			this->scrolling(orientation);
			member.adjustScrollBar();
			bool widthDirty = !member.horScrollHint && areaWidth != area.width();
			bool heightDirty = !member.verScrollHint && areaHeight != area.height();
			if (widthDirty || heightDirty)
			{
				this->adjust();
			}
			this->fresh();
		}
	}
}
const BRect& BScrollArea::area() const
{
	return member.area;
}

void BScrollArea::setOffset(const BPoint& offset)
{
	const BRect& crect = this->centerRect();
	BRect area = member.rawArea;
	area.setPos(crect.pos() + offset);
	this->setArea(area);
}
BPoint BScrollArea::offset() const
{
	return member.area.pos();
}

void BScrollArea::setSpreadSize(int spreadWidth, int spreadHeight)
{
	if (spreadWidth < 0)
		spreadWidth = 0;
	if (spreadHeight < 0)
		spreadHeight = 0;
	return setSpreadSize(BSize(spreadWidth, spreadHeight));
}
void BScrollArea::setSpreadSize(const BSize& spreadSize)
{
	BSizef tempSize = spreadSize;
	if (tempSize.width() < 0)
		tempSize.width() = 0;
	if (tempSize.height() < 0)
		tempSize.height() = 0;
	if (member.spreadSize != spreadSize)
	{
		member.spreadSize = spreadSize;
		if (Orientation orientation = member.adjustRealArea())
		{
			this->scrolling(orientation);
			member.adjustScrollBar();
			bool widthDirty = !member.horScrollHint && member.spreadSize.width() != spreadSize.width();
			bool heightDirty = !member.verScrollHint && member.spreadSize.height() != spreadSize.height();
			if (widthDirty || heightDirty)
			{
				this->adjust();
			}
		}
		this->fresh();
	}
}
const BSize& BScrollArea::spreadSize() const
{
	return member.spreadSize;
}

void BScrollArea::setScrollStep(float scrollStep)
{
	if (member.scrollStep != scrollStep)
	{
		member.scrollStep = scrollStep;
		member.adjustScrollBar();
		this->emit(Signal_StepChanged, scrollStep);
	}
}
float BScrollArea::scrollStep() const
{
	return member.scrollStep;
}

void BScrollArea::setScrollHint(ScrollHint scrollHint)
{
	setVerticalScrollHint(scrollHint);
	setHorizontalScrollHint(scrollHint);
}
void BScrollArea::setScrollHint(ScrollHint horScrollHint, ScrollHint verScrollHint)
{
	setVerticalScrollHint(verScrollHint);
	setHorizontalScrollHint(horScrollHint);
}

void BScrollArea::setVerticalScrollHint(ScrollHint scrollBarPolicy)
{
	if (member.verScrollHint != scrollBarPolicy)
	{
		member.verScrollHint = scrollBarPolicy;
		if (member.verScrollHint == ScrollHint_Floating)
		{
			member.verScrollBar->hide();
		}
		this->fresh();
	}
}
ScrollHint BScrollArea::verticalScrollHint() const
{
	return member.verScrollHint;
}

void BScrollArea::setHorizontalScrollHint(ScrollHint scrollBarPolicy)
{
	if (member.horScrollHint != scrollBarPolicy)
	{
		member.horScrollHint = scrollBarPolicy;
		if (member.horScrollHint == ScrollHint_Floating)
		{
			member.horScrollBar->hide();
		}
		this->fresh();
	}
}
ScrollHint BScrollArea::horizontalScrollHint() const
{
	return member.horScrollHint;
}

void BScrollArea::setVerticalScrollBar(BScrollBar* scrollBar)
{
	if (member.verScrollBar != scrollBar)
	{
		if (member.verScrollBar)
		{
			member.disconnect(member.verScrollBar);
			this->removeChild(member.verScrollBar);
		}
		member.verScrollBar = scrollBar;
		if (member.verScrollBar)
		{
			member.verScrollBar->setParent(this);
			member.verScrollBar->setOrientation(Orientation_Vertical);
			member.connect(member.verScrollBar, Signal_ValueChanged, &member_BScrollArea::slotVerticalScrollValueChanged);
			member.connect(member.verScrollBar, Signal_MouseLeave, &member_BScrollArea::slotMouseLeaveVerticalScrollBar);
		}
	}
}
const BScrollBar* BScrollArea::verticalScrollBar() const
{
	return member.verScrollBar;
}
BScrollBar* BScrollArea::verticalScrollBar()
{
	return member.verScrollBar;
}

void BScrollArea::setHorizontalScrollBar(BScrollBar* scrollBar)
{
	if (member.horScrollBar != scrollBar)
	{
		if (member.horScrollBar)
		{
			member.disconnect(member.horScrollBar);
			this->removeChild(member.horScrollBar);
		}
		member.horScrollBar = scrollBar;
		if (member.horScrollBar)
		{
			scrollBar->setParent(this);
			scrollBar->setOrientation(Orientation_Horizontal);
			member.connect(member.horScrollBar, Signal_ValueChanged, &member_BScrollArea::slotHorizontalScrollValueChanged);
			member.connect(member.horScrollBar, Signal_MouseLeave, &member_BScrollArea::slotMouseLeaveHorizontalScrollBar);
		}
	}
}
const BScrollBar* BScrollArea::horizontalScrollBar() const
{
	return member.horScrollBar;
}
BScrollBar* BScrollArea::horizontalScrollBar()
{
	return member.horScrollBar;
}

bool BScrollArea::scrollTo(Part part)
{
	this->flush();
	const BRect& crect = this->centerRect();
	BPoint pos = member.area.pos();
	if (part & Part_Top)
	{
		pos.y() = crect.top();
	}
	if (part & Part_Bottom)
	{
		pos.y() = crect.bottom() - member.area.height();
	}
	if (part & Part_Left)
	{
		pos.x() = crect.left();
	}
	if (part & Part_Right)
	{
		pos.x() = crect.right() - member.area.width();
	}
	if (member.area.pos() != pos)
	{
		BRect area = member.area;
		area.setPos(pos);
		setArea(area);
		return true;
	}
	return false;
}
bool BScrollArea::scrollToRect(const BRect& rect, Align align)
{
	this->flush();
	const BRect& crect = this->centerRect();
	BRect dest = rect;
	if (align & Align_Horizontal)
	{
		if (align & Align_Left)
			dest.setX(crect.x());
		if (align & Align_Right)
			dest.setX(crect.right() - rect.width());
		if (align & Align_HCenter)
			dest.setX((crect.width() - rect.width()) / 2);
	}
	else
	{
		if (dest.right() > crect.right())
			dest.setX(crect.right() - dest.width());
		if (dest.left() < crect.left())
			dest.setX(crect.x());
	}
	if (align & Align_Vertical)
	{
		if (align & Align_Top)
			dest.setY(crect.y());
		if (align & Align_Bottom)
			dest.setY(crect.bottom() - rect.height());
		if (align & Align_VCenter)
			dest.setY((crect.height() - rect.height()) / 2);
	}
	else
	{
		if (dest.bottom() > crect.bottom())
			dest.setY(crect.bottom() - dest.height());
		if (dest.top() < crect.top())
			dest.setY(crect.y());
	}
	BPoint offset = dest.pos() - rect.pos();
	if (offset.empty())
		return false;
	BRect area = member.area;
	area.move(offset);
	this->setArea(area);
	return true;
}
bool BScrollArea::scrollToPoint(const BPoint& point, Align align)
{
	this->flush();
	const BRect& crect = this->centerRect();
	BPoint dest = point;
	if (align & Align_Horizontal)
	{
		if (align & Align_Left)
			dest.x() = crect.left();
		if (align & Align_Right)
			dest.x() = crect.right();
		if (align & Align_HCenter)
			dest.x() = crect.center().x();
	}
	else
	{
		if (dest.x() > crect.right())
			dest.x() = crect.right();
		if (dest.x() < crect.left())
			dest.x() = crect.left();
	}
	if (align & Align_Vertical)
	{
		if (align & Align_Top)
			dest.y() = crect.y();
		if (align & Align_Bottom)
			dest.y() = crect.bottom();
		if (align & Align_VCenter)
			dest.y() = crect.center().y();
	}
	else
	{
		if (dest.y() > crect.bottom())
			dest.y() = crect.bottom();
		if (dest.y() < crect.top())
			dest.y() = crect.top();
	}
	BPoint offset = dest - point;
	if (offset.empty())
		return false;
	BRect area = member.area;
	area.move(offset);
	this->setArea(area);
	return true;
}
bool BScrollArea::stepScrollTo(Part part)
{
	if (part)
	{
		if (part & Part_Left)
			member.horScrollBar->decrease();
		if (part & Part_Right)
			member.horScrollBar->increase();
		if (part & Part_Top)
			member.verScrollBar->decrease();
		if (part & Part_Bottom)
			member.verScrollBar->increase();
		return true;
	}
	return false;
}

const BString& BScrollArea::realTips() const
{
	if (member.hoverTitle)
		return BWidget::realTips();
	return member.noneTips;
}

BSize BScrollArea::sizeHint() const
{
	if (member.dirty)
	{
		member.dirty = false;
		member.adjustRects();
	}
	BSize csize;
	if (member.titleVisible)
	{
		if (csize.width() < member.alignment.ia_hintSize.width())
			csize.width() = member.alignment.ia_hintSize.width();
		if (csize.height() < member.alignment.ia_hintSize.height())
			csize.height() = member.alignment.ia_hintSize.height();
	}
	if (member.horScrollHint)
	{
		if (csize.width() < 100)
			csize.width() = 100;
	}
	else
	{
		if (csize.width() < member.hintSize.width())
			csize.width() = member.hintSize.width();
	}
	if (member.verScrollHint)
	{
		if (csize.height() < 100)
			csize.height() = 100;
	}
	else
	{
		if (csize.height() < member.hintSize.height())
			csize.height() = member.hintSize.height();
	}
	return this->preferSize(csize);
}

bool BScrollArea::input(const BString& text, const BValue& value)
{
	if (text == "area")
	{
		BRect area = value;
		setArea(area);
		return true;
	}
	if (text == "area-size")
	{
		BSize size = value;
		setAreaSize(size);
		return true;
	}
	return BObject::input(text, value);
}
bool BScrollArea::query(const BString& text, BValue& value) const
{
	if (text == "raw-area")
	{
		value = member.rawArea;
		return true;
	}
	if (text == "area")
	{
		value = member.area;
		return true;
	}
	if (text == "area-size")
	{
		value = member.area.size();
		return true;
	}
	if (text == "header-rect")
	{
		value = member.headerRect;
		return true;
	}
	return BWidget::query(text, value);
}

void BScrollArea::scrolling(Orientation orientation)
{
	this->emit(Signal_Scrolling, orientation);
}

void BScrollArea::resizeEvent(const BEvent& event)
{
	BWidget::resizeEvent(event);
	member.dirty = true;
	Orientation orientation = member.adjustRealArea();
	member.applyScrolling();
	member.adjustScrollBar();
	this->scrolling(orientation);
	this->fresh();
}

void BScrollArea::styleEvent(const BEvent& event)
{
	BWidget::styleEvent(event);
	BStyle* style = event.value();
	member.titleStyle = 0;
	member.titleSize = -1;

	member.autoTop = style->value(Value_Perch_Top).empty();
	member.autoLeft = style->value(Value_Perch_Left).empty();
	member.autoRight = style->value(Value_Perch_Right).empty();
	member.autoBottom = style->value(Value_Perch_Bottom).empty();

	style_member(style)->assign(Value_Icon_Size, member.iconSize);
	style_member(style)->assign(Value_Align, member.alignment.ia_align);
	style_member(style)->assign(Value_Spacing, member.alignment.ia_spacing);

	style_member(style)->assign(Value_Area_Width_Policy, member.areaPolicy.widthPolicy());
	style_member(style)->assign(Value_Area_Height_Policy, member.areaPolicy.heightPolicy());
	style_member(style)->assign(Value_Spread_Size, member.spreadSize);
	style_member(style)->assign(Value_Handle_Size, member.handleSize);

	style_member(style)->assign(Value_Scroll_Step, member.scrollStep);
	style_member(style)->assign(Value_Scroll_Ver_Hint, member.verScrollHint);
	style_member(style)->assign(Value_Scroll_Hor_Hint, member.horScrollHint);

	if (BStyle* titleStyle = style->annex("title"))
	{
		member.titleStyle = titleStyle;

		style_member(titleStyle)->assign(Value_Visible, member.titleVisible);
		style_member(titleStyle)->assign(Value_Icon_Size, member.iconSize);
		style_member(titleStyle)->assign(Value_Height, member.titleSize);

		style_member(titleStyle)->assign(Value_Align, member.alignment.ia_align);
		style_member(titleStyle)->assign(Value_Spacing, member.alignment.ia_spacing);

		style_member(titleStyle)->assign(Value_Margin_Left, member.titleMargin.left());
		style_member(titleStyle)->assign(Value_Margin_Top, member.titleMargin.top());
		style_member(titleStyle)->assign(Value_Margin_Right, member.titleMargin.right());
		style_member(titleStyle)->assign(Value_Margin_Bottom, member.titleMargin.bottom());
	}

	if (member.handleSize < 1)
	{
		if (const BFont* font = this->realFont())
		{
			member.handleSize = font->size() / 2;
		}
	}
	if (member.verScrollBar)
		member.verScrollBar->setHandleSize(member.handleSize);
	if (member.horScrollBar)
		member.horScrollBar->setHandleSize(member.handleSize);

	member.dirty = true;
}
void BScrollArea::freshEvent(const BEvent& event)
{
	if (member.dirty)
	{
		member.dirty = false;
		member.adjustRects();
	}
	BWidget::freshEvent(event);
}
void BScrollArea::paintEvent(const BEvent& event)
{
	BPainter painter(this);
	painter.drawBackground();
	painter.drawForeground();
	painter.drawPerch();

	if (member.titleVisible)
	{
		State state = painter.state();
		painter.setStyle(member.titleStyle);
		if (member.hoverTitle)
			painter.drawTitle(State_Hovered);
		else
			painter.drawTitle();
		if (this->title().size())
		{
			if (painter.setColor(Color_Text, state))
			{
				if (widget_member(this)->t_title.size())
					painter.drawText(member.alignment.ia_textRect, widget_member(this)->t_title);
				else
					painter.drawText(member.alignment.ia_textRect, widget_member(this)->title);
			}
		}
		if (BIcon* icon = this->icon())
		{
			BImage* image = icon->image(state);
			painter.drawImage(member.alignment.ia_iconRect, image);
		}
		painter.setStyle(0);
	}

	if (member.area.valid())
	{
		const BRect& crect = this->centerRect();
		painter.setClip(crect);
		const BImage* image = member.areaImage;
		if (!image)
			image = painter.realStyle()->image(Image_Area);
		if (image)
		{
			painter.drawImage(member.area, image);
		}
		else if (painter.setColor(Color_Area))
		{
			painter.fillRect(member.area);
		}
		painter.setClip(this->size());
	}

	painter.drawMargin();
	painter.drawBorder();
}

void BScrollArea::mouseEnterEvent(const BMouseEvent& mouseEvent)
{
	BWidget::mouseEnterEvent(mouseEvent);
}
void BScrollArea::mouseLeaveEvent(const BMouseEvent& mouseEvent)
{
	BWidget::mouseLeaveEvent(mouseEvent);
	if (member.horScrollHint == ScrollHint_Floating && !member.horScrollBar->visible())
	{
		member.horScrollBar->hide();
	}
	if (member.verScrollHint == ScrollHint_Floating && !member.verScrollBar->visible())
	{
		member.verScrollBar->hide();
	}
}

void BScrollArea::mouseMoveEvent(const BMouseEvent& mouseEvent)
{
	const BRect& crect = this->centerRect();
	const BPoint& pos = mouseEvent.pos();

	if (member.titleVisible)
	{
		bool hoverTitle = this->rect(Part_Top).contain(pos);
		if (member.hoverTitle != hoverTitle)
		{
			member.hoverTitle = hoverTitle;
			this->fresh();
		}
	}

	if (member.horScrollHint == ScrollHint_Floating && crect.width() < member.area.width())
	{
		const BSize& size = member.horScrollBar->size();
		BRect bottomRect = this->rect(Part_Bottom);
		bottomRect.top() = bottomRect.bottom() - size.height();
		member.horScrollBar->setVisible(bottomRect.contain(pos));
	}

	if (member.verScrollHint == ScrollHint_Floating && crect.height() < member.area.height())
	{
		const BSize& size = member.verScrollBar->size();
		BRect rightRect = this->rect(Part_Right);
		rightRect.left() = rightRect.right() - size.width();
		member.verScrollBar->setVisible(rightRect.contain(pos));
	}

	BWidget::mouseMoveEvent(mouseEvent);
}
void BScrollArea::mouseWheelEvent(const BMouseEvent& mouseEvent)
{
	if (!mouseEvent.keyState(Key_Ctrl))
	{
		int wheel = mouseEvent.wheel();
		if (member.verScrollBar)
		{
			int value = member.verScrollBar->value();
			int step = member.verScrollBar->step();
			if (member.verScrollBar->setValue(value - step * wheel))
				mouseEvent.accept();
		}
	}
	BWidget::mouseWheelEvent(mouseEvent);
}
